"""
Copyright 2008 Olivier Belanger

This file is part of Ounk.

Ounk is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Ounk is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Ounk.  If not, see <http://www.gnu.org/licenses/>.
"""

import os, random, time
import wx
from math import cos, sin, atan2, sqrt, pi
try:
    import Image, ImageDraw
    isPIL = True
except:
    print 'Need to install PIL to save stream of .png files to disk'
    isPIL = False

NAME = 'Fractal generator'
VERSION = '1.0'

MAXITER = {'tree2': 14,
            'tree3': 10,
            'tree4': 8,
            'tree5': 6,
            'tree6': 6,
            'flower': 10,
            'koch': 8,
            'flake': 8,
            'weird': 7,
            'fern': 10} 

DEFAULTS = {'tree2': [8, 6000, 600, 270, 150, (110,200)],
            'tree3': [6, 6000, 600, 270, 150, (110,200)],
            'tree4': [6, 6000, 600, 270, 150, (110,200)],
            'tree5': [5, 9000, 250, 270, 150, (110,110)],
            'tree6': [5, 9000, 250, 270, 150, (110,110)],
            'flower': [8, 6000, 600, 270, 150, (110,210)],
            'koch': [6, 9000, 600, 270, 200, (110,200)],
            'flake': [6, 6000, 600, 270, 200, (15,110)],
            'weird': [5, 1200, 650, 270, 80, (110,210)],
            'fern': [8, 9000, 750, 270, 120, (110,210)]}  

def interpFloat(t, v1, v2):
    "interpolator for a single value; interprets t in [0-1] between v1 and v2"
    return (v2-v1)*t + v1

def tFromValue(value, v1, v2):
    "returns a t (in range 0-1) given a value in the range v1 to v2"
    return float(value-v1)/(v2-v1)

def save_png(lines, width, height, count):
    """draw and return the fractal tree image"""
    # create empty white image to draw on
    # to create movie file with pngs:
    # ffmpeg -r 10 -i %07d0.png -r 25 out.mpg
    image1 = Image.new("1", (width, height), 1)
    draw = ImageDraw.Draw(image1)
    for line in lines:
        draw.line(line, 0)
    if count < 10:    
        filename = os.getcwd() + "/imgs/0000000%i.png" % count
    elif count < 100:     
        filename = os.getcwd() + "/imgs/000000%i.png" % count
    elif count < 1000:    
        filename = os.getcwd() + "/imgs/00000%i.png" % count
    elif count < 10000:    
        filename = os.getcwd() + "/imgs/0000%i.png" % count
    elif count < 100000:    
        filename = os.getcwd() + "/imgs/000%i.png" % count
    elif count < 1000000:    
        filename = os.getcwd() + "/imgs/00%i.png" % count        
    elif count < 10000000:    
        filename = os.getcwd() + "/imgs/0%i.png" % count 
    else:
        filename = os.getcwd() + "/imgs/%i.png" % count 
    image1.save(filename)
        
def tree2(iter, origin, t, r, theta, dtheta, randdepth):
    x0, y0 = origin
    rand = random.random 
    x, y = x0 + t * cos(theta), y0 + t * sin(theta)
    lines = [(x0,y0,x,y)]
    
    count1 = 0
    count2 = 1
    extend = lines.extend
    myAtan2 = atan2
    myCos = cos
    mySin = sin
    for i in range(iter-1):
        count2 = len(lines)
        t *= r
        for line in lines[count1:count2]:
            rd = rand() * randdepth - (randdepth * .5)
            a,b,c,d = line[0], line[1], line[2], line[3]
            angle = myAtan2(d-b,c-a)
            theta = angle + dtheta
            x, y = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle - dtheta
            xx, yy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            extend([(c,d,x,y), (c, d, xx, yy)])
        count1 = count2
    return lines

def tree3(iter, origin, t, r, theta, dtheta, randdepth):
    x0, y0 = origin
    rand = random.random 
    x, y = x0 + t * cos(theta), y0 + t * sin(theta)
    lines = [(x0,y0,x,y)]
    
    count1 = 0
    count2 = 1
    extend = lines.extend
    myAtan2 = atan2
    myCos = cos
    mySin = sin
    for i in range(iter-1):
        count2 = len(lines)
        t *= r
        for line in lines[count1:count2]:
            rd = rand() * randdepth - (randdepth * .5)
            a,b,c,d = line[0], line[1], line[2], line[3]
            angle = myAtan2(d-b,c-a)
            theta = angle + dtheta
            x, y = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle - dtheta
            xx, yy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle
            xxx, yyy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd            
            extend([(c,d,x,y), (c, d, xx, yy), (c, d, xxx, yyy)])
        count1 = count2
    return lines

def tree4(iter, origin, t, r, theta, dtheta, randdepth):
    x0, y0 = origin
    rand = random.random 
    x, y = x0 + t * cos(theta), y0 + t * sin(theta)
    lines = [(x0,y0,x,y)]
    
    count1 = 0
    count2 = 1
    extend = lines.extend
    myAtan2 = atan2
    myCos = cos
    mySin = sin
    for i in range(iter-1):
        count2 = len(lines)
        t *= r
        for line in lines[count1:count2]:
            rd = rand() * randdepth - (randdepth * .5)
            a,b,c,d = line[0], line[1], line[2], line[3]
            angle = myAtan2(d-b,c-a)
            theta = angle + dtheta
            x, y = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle - dtheta
            xx, yy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = -(angle + dtheta)
            xxx, yyy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = -(angle - dtheta)
            xxxx, yyyy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            extend([(c,d,x,y), (c, d, xx, yy), (c,d,xxx,yyy), (c, d, xxxx, yyyy)])
        count1 = count2
    del lines[0]    
    return lines
    
def tree5(iter, origin, t, r, theta, dtheta, randdepth):
    x0, y0 = origin
    rand = random.random 
    x, y = x0 + t * cos(theta), y0 + t * sin(theta)
    lines = [(x0,y0,x,y)]
    x, y = x0 + t * cos(theta+(dtheta*.8)), y0 + t * sin(theta+(dtheta*.8))
    lines.append((x0,y0,x,y))
    x, y = x0 + t * cos(theta+(dtheta*1.6)), y0 + t * sin(theta+(dtheta*1.6))
    lines.append((x0,y0,x,y))
    x, y = x0 + t * cos(theta-(dtheta*.8)), y0 + t * sin(theta-(dtheta*.8))
    lines.append((x0,y0,x,y))
    x, y = x0 + t * cos(theta-(dtheta*1.6)), y0 + t * sin(theta-(dtheta*1.6))
    lines.append((x0,y0,x,y))

    
    count1 = 0
    count2 = 1
    extend = lines.extend
    myAtan2 = atan2
    myCos = cos
    mySin = sin
    for i in range(iter-1):
        count2 = len(lines)
        t *= r
        for line in lines[count1:count2]:
            rd = rand() * randdepth - (randdepth * .5)
            a,b,c,d = line[0], line[1], line[2], line[3]
            angle = myAtan2(d-b,c-a)
            theta = angle
            x, y = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle + (dtheta*.8)
            xx, yy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle + (dtheta*1.6)
            xxx, yyy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle - (dtheta*.8)
            xxxx, yyyy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle - (dtheta*1.6)
            xxxxx, yyyyy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            extend([(c,d,x,y), (c, d, xx, yy), (c, d, xxx, yyy), (c, d, xxxx, yyyy), (c, d, xxxxx, yyyyy)])
        count1 = count2
    return lines

def tree6(iter, origin, t, r, theta, dtheta, randdepth):
    x0, y0 = origin
    rand = random.random 
    x, y = x0 + t * cos(theta), y0 + t * sin(theta)
    lines = [(x0,y0,x,y)]
    x, y = x0 + t * cos(theta+(dtheta*.66667)), y0 + t * sin(theta+(dtheta*.66667))
    lines.append((x0,y0,x,y))
    x, y = x0 + t * cos(theta+(dtheta*1.33333)), y0 + t * sin(theta+(dtheta*1.33333))
    lines.append((x0,y0,x,y))
    x, y = x0 + t * cos(theta-(dtheta*.66667)), y0 + t * sin(theta-(dtheta*.66667))
    lines.append((x0,y0,x,y))
    x, y = x0 + t * cos(theta-(dtheta*1.33333)), y0 + t * sin(theta-(dtheta*1.33333))
    lines.append((x0,y0,x,y))
    x, y = x0 + t * cos(theta-(dtheta*2)), y0 + t * sin(theta-(dtheta*2))
    lines.append((x0,y0,x,y))
    
    count1 = 0
    count2 = 1
    extend = lines.extend
    myAtan2 = atan2
    myCos = cos
    mySin = sin
    for i in range(iter-1):
        count2 = len(lines)
        t *= r
        for line in lines[count1:count2]:
            rd = rand() * randdepth - (randdepth * .5)
            a,b,c,d = line[0], line[1], line[2], line[3]
            angle = myAtan2(d-b,c-a)
            theta = angle
            x, y = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle + (dtheta*.8)
            xx, yy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle + (dtheta*1.6)
            xxx, yyy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle - (dtheta*.8)
            xxxx, yyyy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle - (dtheta*1.6)
            xxxxx, yyyyy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            extend([(c,d,x,y), (c, d, xx, yy), (c, d, xxx, yyy), (c, d, xxxx, yyyy), (c, d, xxxxx, yyyyy)])
        count1 = count2
    return lines

def flower(iter, origin, t, r, theta, dtheta, randdepth):
    x0, y0 = origin
    rand = random.random 
    x, y = x0 + t * cos(theta), y0 + t * sin(theta)
    lines = [(x0,y0,x,y)]

    extend = lines.extend
    myAtan2 = atan2
    myCos = cos
    mySin = sin
    for i in range(iter-1):
        t *= r
        linesLen = len(lines)
        for line in lines[0:linesLen]:
            rd = rand() * randdepth - (randdepth * .5)
            a,b,c,d = line[0], line[1], line[2], line[3]
            angle = myAtan2(d-b,c-a)
            theta = angle + dtheta
            x, y = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle - dtheta
            xx, yy = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            extend([(c,d,x,y), (c, d, xx, yy)])
    return lines

def koch(iter, origin, t, r, theta, dtheta, randdepth):
    x0, y0 = origin[0]-250, origin[1]
    t *= 2.5
    rand = random.random 
    x, y = x0 + t * cos(theta+dtheta), y0 + t * sin(theta+dtheta)
    lines = [(x0,y0,x,y)]

    extend = lines.extend
    myAtan2 = atan2
    mySqrt = sqrt
    myCos = cos
    mySin = sin
    eq = r*100*pi/180.0
    for i in range(iter-1):
        linesLen = len(lines)
        for line in lines[0:linesLen]:
            rd = (rand() * randdepth - (randdepth * .5)) * 0.01
            a,b,c,d = line[0], line[1], line[2], line[3]
            angle = myAtan2(d-b,c-a)
            length = (mySqrt((c-a)*(c-a)+(d-b)*(d-b)))/3.            
            x, y = a + length * myCos(angle), b + length * mySin(angle)
            xx, yy = x + length * myCos(angle), y + length * mySin(angle)
            xxx, yyy = xx + length * myCos(angle), yy + length * mySin(angle)

            subx, suby = x + length * myCos(eq-angle+rd), y - length * mySin(eq-angle+rd)
            extend([(a,b,x,y), (x, y, subx, suby), (subx, suby, xx, yy), (xx, yy, xxx, yyy)])
        del lines[0:linesLen]
    return lines

def flake(iter, origin, t, r, theta, dtheta, randdepth):
    x0, y0 = origin
    t *= 2
    rand = random.random 
    firsteq = 60*pi/180.0+theta
    secondeq = 180*pi/180.0+theta
    thirdeq = 300*pi/180.0+theta
    x, y = x0 + t * cos(firsteq), y0 + t * sin(firsteq)
    xx, yy = x + t * cos(secondeq), y + t * sin(secondeq)
    xxx, yyy = xx + t * cos(thirdeq), yy + t * sin(thirdeq)
    lines = [(x0,y0,x,y), (x,y,xx,yy), (xx,yy,xxx,yyy)]

    extend = lines.extend
    myAtan2 = atan2
    mySqrt = sqrt
    myCos = cos
    mySin = sin
    eq = r*100*pi/180.0
    for i in range(iter-1):
        linesLen = len(lines)
        rd = (rand() * randdepth - (randdepth * .5)) * 0.1
        for line in lines[0:linesLen]:            
            a,b,c,d = line[0], line[1], line[2], line[3]
            angle = myAtan2(d-b,c-a)
            length = (mySqrt((c-a)*(c-a)+(d-b)*(d-b)))/3.            
            x, y = a + length * myCos(angle), b + length * mySin(angle)
            xx, yy = x + length * myCos(angle), y + length * mySin(angle)
            xxx, yyy = xx + length * myCos(angle), yy + length * mySin(angle)

            subx, suby = x + length * myCos(eq-angle+rd), y - length * mySin(eq-angle+rd)
            extend([(a,b,x,y), (x, y, subx, suby), (subx, suby, xx, yy), (xx, yy, xxx, yyy)])
        del lines[0:linesLen]
    return lines

def weird(iter, origin, t, r, theta, dtheta, randdepth):
    x0, y0 = origin
    rand = random.random 
    x, y = x0 + t * cos(theta), y0 + t * sin(theta)
    lines = [(x0,y0,x,y)]
    x, y = x0 + t * cos(theta+(dtheta*.66667)), y0 + t * sin(theta+(dtheta*.66667))
    lines.append((x0,y0,x,y))
    x, y = x0 + t * cos(theta+(dtheta*1.33333)), y0 + t * sin(theta+(dtheta*1.33333))
    lines.append((x0,y0,x,y))
    x, y = x0 + t * cos(theta-(dtheta*.66667)), y0 + t * sin(theta-(dtheta*.66667))
    lines.append((x0,y0,x,y))
    x, y = x0 + t * cos(theta-(dtheta*1.33333)), y0 + t * sin(theta-(dtheta*1.33333))
    lines.append((x0,y0,x,y))
    x, y = x0 + t * cos(theta-(dtheta*2)), y0 + t * sin(theta-(dtheta*2))
    lines.append((x0,y0,x,y))
    
    count1 = 0
    count2 = 1
    extend = lines.extend
    myAtan2 = atan2
    myCos = cos
    mySin = sin
    for i in range(iter-1):
        count2 = len(lines)
        t *= r
        for line in lines[count1:count2]:
            rd = rand() * randdepth - (randdepth * .5)
            a,b,c,d = line[0], line[1], line[2], line[3]
            angle = myAtan2(d-b,c-a)
            theta = angle
            x, y = c + t * myCos(theta) + rd, d + t * mySin(theta) + rd
            theta = angle + (dtheta*.8)
            xx, yy = x + t * myCos(theta) + rd, y + t * mySin(theta) + rd
            theta = angle + (dtheta*1.6)
            xxx, yyy = xx + t * myCos(theta) + rd, yy + t * mySin(theta) + rd
            theta = angle - (dtheta*.8)
            xxxx, yyyy = xxx + t * myCos(theta) + rd, yyy + t * mySin(theta) + rd
            extend([(c,d,x,y), (x, y, xx, yy), (xx, yy, xxx, yyy), (xxx, yyy, xxxx, yyyy)])
        count1 = count2
    return lines

def fern(iter, origin, t, r, theta, dtheta, randdepth):
    x0, y0 = origin
    rand = random.random 
    x, y = x0 + t * cos(theta), y0 + t * sin(theta)
    lines = [(x0,y0,x,y)]
    
    count1 = 0
    count2 = 1
    extend = lines.extend
    myAtan2 = atan2
    mySqrt = sqrt
    myCos = cos
    mySin = sin
    for i in range(iter-1):
        count2 = len(lines)
        t *= r
        for line in lines[count1:count2]:
            rd = rand() * randdepth - (randdepth * .5)
            a,b,c,d = line[0], line[1], line[2], line[3]
            angle = myAtan2(d-b,c-a)
            length = (mySqrt((c-a)*(c-a)+(d-b)*(d-b)))            
            theta = angle + dtheta
            x, y = c + (length*.4*r) * myCos(theta+0.1) + rd, d + (length*.4*r) * mySin(theta+0.1) + rd
            theta = angle - dtheta
            xx, yy = c + (length*.4*r) * myCos(theta+0.1) + rd, d + (length*.4*r) * mySin(theta+0.1) + rd
            theta = angle
            xxx, yyy = c + (length*r) * myCos(theta+0.1) + rd, d + (length*r) * mySin(theta+0.1) + rd            
            extend([(c,d,x,y), (c, d, xx, yy), (c, d, xxx, yyy)])
        count1 = count2
    return lines

class MainPanel(wx.Panel):
    def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.SIMPLE_BORDER):
        wx.Panel.__init__(self, parent, ID, pos, size, style)
        self.mainFrame = parent
        self.iter = 5 # experiment with number of iterations (try 4 to 16)
        self.angle = 60 # experiment with angle of the branch (try 60 deg)
        self.cont = 0.6 # experiment with factor to contract the trunk each iteration (try 0.65)
        self.dir = 270 # starting orientation (initial 90 deg)
        self.trunc = 80 # experiment with trunk length (try 100)
        self.origin = (size[0]/2, size[1]*.8)
        self.algo = 'tree2'
        self.rand = 1
        self.rec = False
        self.count = 0
        self.calculate()
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_TIMER, self.onTimer)   

    def calculate(self, evt=None):
        ang2rad = 0.017453292519943295 #math.pi/180.0        
        theta = self.dir * ang2rad
        dtheta = self.angle * ang2rad
        func = eval(self.algo)
        self.lines = func(self.iter, self.origin, self.trunc, self.cont, theta, dtheta, self.rand)

        if isPIL: # faster (with OnPaint2) but cheaper image quality
            self.image = Image.new("1", (500, 500),1)
            draw = ImageDraw.Draw(self.image)
            for line in self.lines:
                draw.line(line, 0)
            self.bit = wx.EmptyBitmap(500,500)
            self.bit.CopyFromBuffer(self.image.convert("RGB").tostring())

        self.Refresh(True)
        self.Update()

    def OnPaint(self, lines):       
        if isPIL:
            dc = wx.ClientDC(self)
            dc.Clear()
            dc.DrawBitmap(self.bit, 0, 0)
            if self.mainFrame.function:
                self.mainFrame.function()
        else:
            self.dc = wx.ClientDC(self)
            self.dc.Clear()
            bufdc = wx.BufferedDC(self.dc)
            bufdc.Clear()
            bufdc.DrawLineList(self.lines)
            bufdc.UnMask()
       
        if self.rec and isPIL:
            save_png(self.lines, 500, 500, self.count)
            self.count += 1
            
    def iterVal(self, evt):
        self.iter = evt.GetInt()
        self.calculate()
        evt.Skip()

    def angleVal(self, evt):
        self.angle = evt.GetInt() * 0.01
        self.calculate()
        evt.Skip()

    def contractVal(self, evt):
        self.cont = evt.GetInt() * 0.001
        self.calculate()
        evt.Skip()

    def dirVal(self, evt):
        self.dir = evt.GetInt()
        self.calculate()
        evt.Skip()

    def truncVal(self, evt):
        self.trunc = evt.GetInt()
        self.calculate()
        evt.Skip()

    def randVal(self, evt):
        self.rand = evt.GetInt() * 0.1
        self.calculate()
        evt.Skip()
        
    def timertog(self, evt):
        if evt.GetInt():
            self.randtimer = wx.Timer(self, -1)
            self.randtimer.Start(100)
        else:
            self.randtimer.Stop()
            del self.randtimer
        self.calculate()
        evt.Skip()        

    def setOrigin(self, point):
        if self.origin != point:
            self.origin = point
            self.calculate()

    def rectog(self, evt):
        if evt.GetInt():
            self.rec = True
        else:
            self.rec = False
            os.system("ffmpeg -r 10 -sameq -minrate 5000 -i " + os.getcwd() + "/imgs/%07d0.png -r 25 " + os.getcwd() + "/movs/out.mpg")
            os.system("rm " + os.getcwd() + "/imgs/*.png")

    def handlemenu(self, evt):
        self.algo = evt.GetString()
        self.mainFrame.iterslider.SetMax(MAXITER[self.algo])
        self.mainFrame.iterslider.SetValue(DEFAULTS[self.algo][0])
        self.iter = DEFAULTS[self.algo][0]
        self.mainFrame.angleslider.SetValue(DEFAULTS[self.algo][1])
        self.angle = DEFAULTS[self.algo][1] * 0.01
        self.mainFrame.contractslider.SetValue(DEFAULTS[self.algo][2])
        self.cont = DEFAULTS[self.algo][2] * 0.001
        self.mainFrame.dirslider.SetValue(DEFAULTS[self.algo][3])
        self.dir = DEFAULTS[self.algo][3]
        self.mainFrame.lengthslider.SetValue(DEFAULTS[self.algo][4])
        self.trunc = DEFAULTS[self.algo][4]
        self.mainFrame.originslider.SetValue(DEFAULTS[self.algo][5])
        self.mainFrame.iterslider.Validate()
        self.calculate()
        
    def onTimer(self, evt):
        self.calculate()
        
class Fractals(wx.Frame):
    def __init__(self, parent, ID, title, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, function=None):
        wx.Frame.__init__(self, parent, ID, title, pos, size, style)

        self.Bind(wx.EVT_CLOSE, self.OnClose)
        
        self.function = function
        
        if wx.Platform == '__WXMAC__':
            self.MacSetMetalAppearance(True)
            
        BORDER = 5
        
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        leftBox = wx.BoxSizer(wx.VERTICAL)
        sliderBox = wx.BoxSizer(wx.VERTICAL)
        self.panel = MainPanel(self, wx.ID_ANY, pos=(0,0), size=(500,500))
        leftBox.Add(self.panel)
    
        ibox = wx.BoxSizer(wx.VERTICAL)
        itext = wx.StaticText(self, -1, 'Number of iterations')
        self.iterslider = wx.Slider( self, -1, 5, 2, 14, wx.DefaultPosition, size=(250,50), 
                        style=wx.HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS)
        self.iterslider.SetTickFreq(5, 1)
        ibox.Add(itext)
        ibox.Add(self.iterslider)

        abox = wx.BoxSizer(wx.VERTICAL)
        atext = wx.StaticText(self, -1, 'Angle of junctions')
        self.angleslider = wx.Slider( self, -1, 6000, 0, 9000, wx.DefaultPosition, size=(250,50), 
                        style=wx.HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS)
        self.angleslider.SetTickFreq(5, 1)
        abox.Add(atext)
        abox.Add(self.angleslider)
        
        cbox = wx.BoxSizer(wx.VERTICAL)
        ctext = wx.StaticText(self, -1, 'Contract branchs')
        self.contractslider = wx.Slider( self, -1, 600, 250, 1000, wx.DefaultPosition, size=(250,50), 
                        style=wx.HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS)
        self.contractslider.SetTickFreq(5, 1)
        cbox.Add(ctext)
        cbox.Add(self.contractslider)

        dbox = wx.BoxSizer(wx.VERTICAL)
        dtext = wx.StaticText(self, -1, 'Starting angle')
        self.dirslider = wx.Slider( self, -1, 270, 0, 360, wx.DefaultPosition, size=(250,50), 
                        style=wx.HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS)
        self.dirslider.SetTickFreq(5, 1)
        dbox.Add(dtext)
        dbox.Add(self.dirslider)

        tbox = wx.BoxSizer(wx.VERTICAL)
        ttext = wx.StaticText(self, -1, 'Trunk length')
        self.lengthslider = wx.Slider( self, -1, 100, 0, 250, wx.DefaultPosition, size=(250,50), 
                        style=wx.HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS)
        self.lengthslider.SetTickFreq(5, 1)
        tbox.Add(ttext)
        tbox.Add(self.lengthslider)

        timerbox = wx.BoxSizer(wx.HORIZONTAL)
        timerbox.AddSpacer((10,10))

        rbox = wx.BoxSizer(wx.VERTICAL)
        rtext = wx.StaticText(self, -1, 'Rand Depth')
        rslider = wx.Slider( self, -1, 10, 0, 100, wx.DefaultPosition, size=(250,50), 
                        style=wx.HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS)
        rslider.SetTickFreq(5, 1)
        rbox.Add(rtext)
        rbox.Add(rslider)
        timerbox.Add(rbox, 0, border=BORDER)
        
        timerbox.AddSpacer((0,10))

        timertoggle = wx.ToggleButton(self, -1, size=(70,40))
        timertoggle.SetLabel('Start random')
        timerbox.Add(timertoggle, 0, wx.TOP, border=BORDER+5)

        timerbox.AddSpacer((10,10))

        rectoggle = wx.ToggleButton(self, -1, size=(70,40))
        rectoggle.SetLabel('Start record')
        if not isPIL:
            rectoggle.Disable()
        timerbox.Add(rectoggle, 0, wx.TOP, border=BORDER+5)

        timerbox.AddSpacer((10,10))

        brbox = wx.BoxSizer(wx.VERTICAL)
        brtext = wx.StaticText(self, -1, 'Algorithm')      
        brmenu = wx.Choice(self, -1, 
                            choices=['tree2', 'tree3','tree4','tree5', 'tree6', 
                                     'flower', 'koch', 'flake', 'weird', 'fern'])
        brbox.AddSpacer((10,10))
        brbox.Add(brtext)
        brbox.AddSpacer((2,2))
        brbox.Add(brmenu)
        timerbox.Add(brbox, 0, border=BORDER)
        
        pbox = wx.BoxSizer(wx.VERTICAL)
        ptext = wx.StaticText(self, -1, 'Origin')       
        self.originslider = XYSlider(self, 0, 500, size=(220,220), function=self.panel.setOrigin)
        pbox.Add(ptext)
        pbox.AddSpacer((2,2))
        pbox.Add(self.originslider)
        
        sliderBox.Add(ibox, border=BORDER)
        sliderBox.Add(abox, border=BORDER)
        sliderBox.Add(cbox, border=BORDER)
        sliderBox.Add(dbox, border=BORDER)
        sliderBox.Add(tbox, border=BORDER)
        sliderBox.AddSpacer((0,4))
        sliderBox.Add(pbox, 0, wx.LEFT, border=BORDER)
        leftBox.Add(timerbox, 0, wx.TOP, border=BORDER)
        sizer.Add(leftBox, 0, wx.ALL, border=BORDER)
        sizer.Add(sliderBox, 0, wx.ALL, border=BORDER)
        self.Bind(wx.EVT_SLIDER, self.panel.iterVal, self.iterslider)
        self.Bind(wx.EVT_SLIDER, self.panel.angleVal, self.angleslider)
        self.Bind(wx.EVT_SLIDER, self.panel.contractVal, self.contractslider)
        self.Bind(wx.EVT_SLIDER, self.panel.dirVal, self.dirslider)
        self.Bind(wx.EVT_SLIDER, self.panel.truncVal, self.lengthslider)
        self.Bind(wx.EVT_SLIDER, self.panel.randVal, rslider)
        self.Bind(wx.EVT_TOGGLEBUTTON, self.panel.timertog, timertoggle)
        self.Bind(wx.EVT_TOGGLEBUTTON, self.panel.rectog, rectoggle)
        self.Bind(wx.EVT_CHOICE, self.panel.handlemenu, brmenu)

        self.SetSizer(sizer)

    def OnClose(self, evt):
        if isPIL:
            del self.panel.image
            del self.panel.bit
        self.Destroy()
        
    def getDepth(self):
        """Return a value between 0 and 1 representing the quantity of black on the screen"""
        if isPIL:
            list = self.panel.image.getcolors()
            for val in list:
                if val[1] == 0:
                    return val[0] / 500000.
        else:
            pass
        
    def scanImage(self, column):
        """Return a list of 0 (white) and 1 (black) for all pixels in the specified column. 
The list has a length of 500."""
        if isPIL:
            pix = self.panel.image.load()
            list = [1-pix[column, i] for i in range(500)]
            list.reverse()
            return list
        else:
            pass
            
class XYSlider(wx.Panel):
    def __init__(self, parent, minvalue=0, maxvalue=100, pos=(0,0), size=(200,200), function=None):
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY, pos=pos, size=size)
        self.outFunction = function
        self.SetRange(minvalue, maxvalue)
        self.buttonWidth = 10
        self.borderWidth = 1
        self.size = size
        self.pos = (size[0]/2,size[1]*.8)
        self.SetColors(outline=(255,255,255), bg=(0,0,0), fill=(255,255,255))
        self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown)
        self.Bind(wx.EVT_LEFT_UP, self.MouseUp)
        self.Bind(wx.EVT_MOTION, self.MouseMotion)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def SetColors(self, outline, bg, fill):
        self.outlinecolor = wx.Color(*outline)
        self.backgroundcolor = wx.Color(*bg)
        self.fillcolor = wx.Color(*fill)
        
    def SetRange(self, minvalue, maxvalue):   
        self.minvalue = minvalue
        self.maxvalue = maxvalue

    def SetValue(self, pos):
        self.pos = pos
        self.Refresh(True)
        
    def GetValue(self):
        x,y = self.pos
        offset = self.buttonWidth/2 + self.borderWidth
        tx = tFromValue(x, offset, self.size[0]-offset)
        xval = interpFloat(tx, self.minvalue, self.maxvalue)
        ty = tFromValue(y, offset, self.size[1]-offset)
        yval = interpFloat(ty, self.minvalue, self.maxvalue)
        return (xval,yval)
        
    def MouseDown(self, evt):
        self.pos = self.clipPos(evt.GetPosition())
        self.CaptureMouse()
        self.Refresh()

    def MouseUp(self, evt):
        self.pos = self.clipPos(evt.GetPosition())
        if self.HasCapture():
            self.ReleaseMouse()
        self.Refresh()

    def MouseMotion(self, evt):
        if evt.Dragging() and evt.LeftIsDown():
            self.pos = self.clipPos(evt.GetPosition())
            self.Refresh()

    def clipPos(self, pos):
        x,y = pos
        sizex, sizey = self.size
        offset = self.buttonWidth/2 + self.borderWidth        
        if x < offset: x = offset
        elif x > (sizex-offset): x = sizex - offset
        else: x = x 
        if y < offset: y = offset
        elif y > (sizey-offset): y = sizey - offset
        else: y = y
        return (x,y)
        
    def OnPaint(self, evt):
        x,y = (0,0)
        w,h = self.GetSize()
        dc = wx.PaintDC(self)

        dc.SetBrush(wx.Brush(self.backgroundcolor, wx.SOLID))
        dc.Clear()

        dc.SetPen(wx.Pen(self.outlinecolor, width=self.borderWidth, style=wx.SOLID))
        dc.DrawRectangle(x, y, w, h)

        b = self.borderWidth
        dc.SetBrush(wx.Brush(self.fillcolor, wx.SOLID))
        dc.SetPen(wx.Pen(self.fillcolor, width=b, style=wx.SOLID))
 
        px, py = self.pos
        dc.DrawRoundedRectangle(px-(self.buttonWidth/2),py-(self.buttonWidth/2),self.buttonWidth,self.buttonWidth, 2)

        if self.outFunction:
            self.outFunction(self.GetValue())

        evt.Skip()        
 
if __name__ == '__main__':
    try:
        import psyco
        psyco.full()
        print "Fractal Uses Psyco"
    except ImportError:
        print "Psyco not found"   
    print '%s %s by Olivier Belanger' % (NAME, VERSION)
    app = wx.PySimpleApp()
    frame = Fractals(None, -1, title=NAME, size=(770, 600))
    frame.Show()         
    app.MainLoop()
